#pragma once

#include <charconv>
#include <memory>
#include <exception>
#include <algorithm>
#include <functional>
#include <set>
#include <map>
#include <cassert>

#include <iostream>
#include <iomanip>
/**
 * Math operator, with priority
 */
struct MathOperator {
    std::string_view text;
    size_t priority;
    std::function<double( double, double )> apply;

    bool operator==( const MathOperator & other ) const { return priority == other.priority; }
    bool operator<( const MathOperator & other ) const { return priority < other.priority; }
};

/**
 * Math expression
 */
struct MathExpression {
    MathExpression( bool n ) : negation( n ) { }
    virtual ~MathExpression() {}
    void negate( bool n ) { negation ^= n; }
    virtual double eval() const = 0;
    bool negation;
};

class ValueExpression : public MathExpression {
    const double value;
public:
    ValueExpression( double v, bool n ) : MathExpression( n ), value( v ) {}
    double eval() const override { return value * ( -negation + !negation ); }
};

class OpExpression : public MathExpression {
    const std::pair<std::unique_ptr<const MathExpression>, std::unique_ptr<const MathExpression>> values;
    const std::unique_ptr<const MathOperator> op;

public:
    OpExpression( std::unique_ptr<MathExpression> first, std::unique_ptr<MathExpression> second, std::unique_ptr<const MathOperator> mathOp )
        : MathExpression( false ), values{std::move( first ), std::move( second )}, op( std::move(mathOp) ) { }
    double eval() const override { return op->apply( values.first->eval(), values.second->eval() ) * ( -negation + !negation ); }
};

/**
 * NOTE workaround for from_chars with T = float type
 * return double value and new position
 */
std::tuple<double, size_t> extractDouble( std::string_view str, size_t first = 0 ) {
    constexpr std::string_view digits{"0123456789"};
    constexpr auto pow = []( size_t n ) { int v = 1; while( n ) { v *= 10; n--; }; return v; };

    double res{};
    int value{};
    size_t pos = std::min( str.find_first_not_of( digits, first ), str.size() );
    std::from_chars( str.data() + first, str.data() + pos, value );
    res = value;

    if( pos != str.size() && str.size() > pos + 1 && str[pos] == '.' && str.find_first_not_of( digits, pos + 1 ) != pos + 1 ) {
        first = pos + 1;
        pos = std::min( str.find_first_not_of( digits, first ), str.size() );
        std::from_chars( str.data() + first, str.data() + pos, value );
        res += static_cast<double>( value ) / pow( pos - first );
    }

    // check for last dot
    if( res > 0 && pos < str.size() && str[pos] == '.' )
        pos++;

    return std::make_tuple( res, pos );
}

std::tuple<std::unique_ptr<MathExpression>, size_t> parse( std::string_view str, size_t pos = 0, bool parentheses = false, size_t depth = 0 ) {
    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Parsing '" << std::string_view{str.data() + pos, str.size() - pos} << "'" << std::endl;
    enum class STATE {
        POPEN,
        PCLOSE,
        OPERATOR,
        NEGATE,
        EXPRESSION,
    };

    /* NOTE supported operators */
    const std::multiset<MathOperator> MathOps {
        {"-", 5, std::minus() },
        {"+", 5, std::plus() },
        {"*", 4, std::multiplies() },
        {"/", 4, std::divides() },
    };

    /* NOTE fill map for working */
    std::map<size_t, std::pair<std::unique_ptr<MathExpression>, const MathOperator *>> MathMap;
    for( const auto & o : MathOps ) {
        MathMap.insert( { o.priority, std::make_pair( nullptr, nullptr )} );
    }

    auto combine = [&depth]( auto & MathMap, const MathOperator * const op, std::unique_ptr<MathExpression> tmp ) {
        assert( tmp );
        for( auto & [priority, p] : MathMap ) {
            auto & [expr, exprOp] = p;

            // NOTE valid checks
            if( ( expr && !exprOp ) || ( !expr && exprOp ) ) {
                throw std::logic_error{"Invalid math map"};
            } else if( !tmp ) {
                throw std::logic_error{"Combine with empty expression"};
            }

            if( expr && exprOp ) {
                if( op && *op == *exprOp ) {
                    /* NOTE merge expr and tmp to new expr, replace exprOp by op */
                    expr = std::make_unique<OpExpression>( std::move( expr ), std::move( tmp ), std::make_unique<const MathOperator>(*exprOp) );
                    exprOp = op;
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "MathMap: stop on operator '" << exprOp->text << "', new value = " << expr->eval() << std::endl;
                    break;
                } else {
                    /* NOTE merge to tmp, clean expr and exprOp */
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "MathMap: merging to tmp, expr = '" << expr->eval() << "', op='" << exprOp->text << "', tmp = " << tmp->eval();
                    tmp = std::make_unique<OpExpression>( std::move( expr ), std::move( tmp ), std::make_unique<const MathOperator>(*exprOp) );
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << " , result = " << tmp->eval() << std::endl;
                    exprOp = nullptr;
                }
            } else if( op && op->priority == priority ) {
                /* NOTE write to the empty value */
                exprOp = op;
                expr = std::move( tmp );
                std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "MathMap: new expr = '" << expr->eval() << "' stored with op='" << exprOp->text << std::endl;
                break;
            }
        }
        assert( tmp || op );
        return tmp;
    };

    /* NOTE store negate effect and temp expression */
    std::unique_ptr<MathExpression> tmpExpr;
    bool negate = false;

    STATE next = STATE::NEGATE;
    const MathOperator * op;
    bool stop = false;

    while( pos < str.size() && !stop ) {
        switch( next ) {
            case STATE::NEGATE:

                /* NOTE negation check, do not throw */
                if( str[pos] == '-' )
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", negate: " << negate << " -> " << ( str[pos] == '-' ) << std::endl;

                negate = str[pos] == '-';
                pos += negate;
                next = STATE::POPEN;
                break;

            case STATE::POPEN:

                /* NOTE open brackets, recursive call, can throw */
                if( str[pos] == '(' ) {
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", open brackets on pos = " << pos << std::endl;
                    std::tie( tmpExpr, pos ) = parse( str, ++pos, true, depth + 1 );
                    tmpExpr->negate(negate);
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", value in brackets = " << tmpExpr->eval() << std::endl;
                    next = STATE::PCLOSE;
                } else {
                    next = STATE::EXPRESSION;
                }

                break;

            case STATE::PCLOSE:

                /* NOTE closing brackets, do not throw */
                if( parentheses && str[pos] == ')' ) {
                    assert( tmpExpr );
                    std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", closing brackets on pos = " << pos << std::endl;
                    stop = true;
                    parentheses = false;
                    pos++;
                }

                next = STATE::OPERATOR;
                break;

            case STATE::OPERATOR:
                /* NOTE operator detect, can throw */
            {
                const auto checkOp = [&](const auto & o) {
                    if( str.size() - pos > o.text.size() && o.text == std::string_view{ str.data() + pos, o.text.size() } ) {
                        std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", detect operator '" << o.text << "'" << std::endl;
                        pos += o.text.size();
                        op = &o;
                        combine( MathMap, &o, std::move( tmpExpr ) );
                        return true;
                    }
                    return false;
                };
                if ( !std::any_of( MathOps.cbegin(), MathOps.cend(), checkOp ) ) {
                    throw std::logic_error{"Unsupported math operator, pos = " + std::to_string( pos ) };
                }
            }

            next = STATE::NEGATE;
            break;

            case STATE::EXPRESSION:
                /* NOTE value expression */
                assert( !tmpExpr );
                {
                    auto [v, nextPos] = extractDouble( str, pos );

                    if( nextPos > pos ) {
                        std::cout << std::setw( static_cast<int>(depth) * 2 ) << "" << "Pos =" << std::setw( 3 ) << pos << ", detect value = " << v << std::endl;
                        pos = nextPos;
                        tmpExpr = std::make_unique<ValueExpression>( v, negate );
                    } else {
                        throw std::logic_error{"Cannot get value on pos = " + std::to_string( pos )};
                    }
                }
                next = STATE::PCLOSE;
                break;
        }
    }

    if( parentheses )
        throw std::logic_error{"Unclosed brackets"};

    return std::make_tuple( combine( MathMap, nullptr, std::move( tmpExpr ) ), pos );
}

double calc(std::string expression) {
    expression.erase(std::remove_if( expression.begin(), expression.end(), isspace ), expression.end());
    auto [v, p] = parse(expression);
    return v->eval();
}
